#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# EffectsSystem.py
# Copyright (c) 2010 - 2011 Matthew Reid

from Bases import BaseObject, OSCBaseObject, ChildGroup, Category, setID, MasterClock
from sequencer import Sequencer
from snapshot import Snapshot
from effectpalette import EffectPalette
from categorypalette import CategoryPaletteGroup

from macros import MACROS

PALETTE_CATEGORIES = ('Dimmer', 'Beam', 'Color', 'Position')

class EffectsSystem(OSCBaseObject):
    _saved_class_name = 'Effects'
    #_saved_child_objects = ['EffectPalettes', 'Faders']
    _saved_child_objects = ['CategoryPalettes']
    def __init__(self, **kwargs):
        self.saved_child_classes = [EffectPalette]
        self.root_category = Category(name='effects_root', id='effects_root')
        self.MainController = kwargs.get('MainController')
        self.DeviceSystem = self.MainController.DeviceSystem
        self.EffectPalettes = ChildGroup(name='EffectPalettes', 
                                         child_class=EffectPalette, 
                                         ignore_index=True)
        self.AppliedPalettes = ChildGroup(name='AppliedPalettes', 
                                          child_class=EffectPalette, 
                                          ignore_index=True)
        self.Faders = ChildGroup(name='Faders')
        self.Shuttles = ChildGroup(name='Shuttles')
        super(EffectsSystem, self).__init__(**kwargs)
        archive = self.MainController.Archive
        for key in self.saved_child_objects:
            archive.add_member(name=key, 
                               path='EffectsSystem', 
                               filename='%s.js' % (key), 
                               serialize_obj={'EffectsSystem':self}, 
                               serialize_kwargs={'EffectsSystem':{'saved_child_objects':[key]}})
        self.CategoryPalettes = ChildGroup(name='CategoryPalettes', 
                                           child_class=CategoryPaletteGroup, 
                                           osc_parent_node=self.osc_node, 
                                           osc_address='CategoryPalettes')
        self.clock = kwargs.get('clock')
        if not self.clock:
            self.clock = MasterClock()
        self.clock.start()
        self.Sequencers = {}
        if 'deserialize' not in kwargs:
            #self.Palettes = {}
            #self.EffectPalettes = {}
            #self.AppliedPalettes = {}
            p = self.add_palette(id='Fade', name='Fade', all_groups=True)
            e = p.add_effect(controller_type='Keyframe', name='Fade', id='Fade')
            #e.controller_prototype.add_keyframes({0.0:0.0, 1.0:1.0})
            #self.Faders = {}
            p = self.add_palette(id='Shuttle', name='Shuttle')
            e = p.add_effect(controller_type='Functional', name='Shuttle', id='Functional')
            #self.Shuttles = {}
            category = self.DeviceSystem.root_category.subcategories['attribute']
            for key in PALETTE_CATEGORIES:
                self.CategoryPalettes.add_child(name=key, 
                                                attrib_category=category, 
                                                MainController=self.MainController, 
                                                EffectsSystem=self, 
                                                osc_address=key)
        self.Macros = {}
        for key, val in MACROS.iteritems():
            m = val(MainController=self.MainController)
            self.Macros[m.name] = m
        self._osc_dumps_pending = {}
        self.add_osc_handler(callbacks={'request-dump':self.on_osc_request_dump, 
                                        'dump-response':self.on_osc_dump_response, 
                                        'child-added':self.on_osc_child_added})
        
    def add_palette(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            
        '''
        kwargs.setdefault('root_category', self.root_category)
        #p = EffectPalette(**kwargs)
        p = self.EffectPalettes.add_child(**kwargs)
        return p
        
    def del_palette(self, **kwargs):
        pid = kwargs.get('id')
        palette = kwargs.get('palette')
        if not palette:
            palette = self.EffectPalettes.get(pid)
        palette.unlink()
        self.EffectPalettes.del_child(palette)
        
    def add_fader(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            duration :
        '''
        kwargs.update({'palette_id':'Fade', 'loop_mode':'single', 'auto_start':False})
        f = self.apply_palette(**kwargs)
        self.Faders.add_child(existing_object=f)
        return f
        
    def edit_fader(self, **kwargs):
        '''
        :Parameters:
            fader :
            fader_id :
            objects : {'objid':{'obj':obj, 'start':startval, 'end':endval}}
        '''
        fader_id = kwargs.get('fader_id')
        fader = kwargs.get('fader')
        objects = kwargs.get('objects')
        if not fader and fader_id is not None:
            fader = self.Faders[fader_id]
        e = fader.effects['Fade']
        for key, val in objects.iteritems():
            if key not in fader.obj_ids:
                fader.add_obj(val['obj'])
            c = e.controllers[key]['Dimmer']
            start = val['start'] / c.obj_scale_factor
            end = val['end'] / c.obj_scale_factor
            c.add_keyframes({0.0:start, 1.0:end})
            
    def del_fader(self, **kwargs):
        id = kwargs.get('id')
        fader = kwargs.get('fader')
        if not id and fader is not None:
            id = fader.id
        if not fader:
            fader = self.Faders[id]
        self.clear_palette(palette=fader)
        self.Faders.del_child(fader)
        #if id in self.Faders:
        #    del self.Faders[id]
        
    def add_shuttle(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            duration :
            group_names :
        '''
        kwargs.update({'palette_id':'Shuttle', 'loop_mode':'up', 'auto_start':False})
        s = self.apply_palette(**kwargs)
        for name in kwargs.get('group_names', []):
            s.add_group_name(name)
        #self.Shuttles.update({s.id:s})
        self.Shuttles.add_child(existing_object=s)
        return s
    
    def add_sequencer(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            duration : float, duration in seconds
            mode : str, loop mode ('single', 'updn', 'up', 'dn')
        '''
        kwargs.setdefault('root_category', self.root_category)
        kwargs.setdefault('clock', self.clock)
        sq = Sequencer(**kwargs)
        sq.connect('state_changed', self.on_sequencer_state_changed)
        self.Sequencers.update({sq.id:sq})
        return sq
        
    def del_sequencer(self, **kwargs):
        id = kwargs.get('id')
        sq = kwargs.get('sequencer')
        if not sq:
            sq = self.Sequencers.get('id')
        sq.stop()
        if sq.id in self.Sequencers:
            del self.Sequencers[sq.id]
            #print 'sequencer deleted'
        
    def apply_palette(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            palette_id :
            palette :
            loop_mode :
            duration :
            objects : list of groups or attributes
        '''
        id = kwargs.get('id')
        palette_id = kwargs.get('palette_id')
        palette = kwargs.get('palette')
        sq = kwargs.get('sequencer')
        mode = kwargs.get('loop_mode', 'updn')
        objects = kwargs.get('objects', [])
        auto_start = kwargs.get('auto_start', True)
        new_kwargs = kwargs.copy()
        for key in ['palette_id', 'palette', 'loop_mode', 'objects']:
            if key in new_kwargs:
                del new_kwargs[key]
        if not palette and palette_id is not None:
            palette = self.EffectPalettes.get(palette_id)
        if palette:
            duration = kwargs.get('duration', palette.default_duration)
            if sq is None:
                sq = self.add_sequencer(duration=duration, mode=mode)
            p_kwargs = palette._get_saved_attr()['attrs'].copy()
            p_kwargs['obj_ids'].clear()
            p_kwargs.update({'sequencer':sq, 'prototype':palette})
            p_kwargs.update(new_kwargs)
            #new_p = EffectPalette(**p_kwargs)
            new_p = self.AppliedPalettes.add_child(**p_kwargs)
            palette.connect('settings_changed', new_p.on_settings_changed)
            for obj in objects:
                new_p.add_obj(obj)
            if auto_start:
                sq.start()
            #self.AppliedPalettes.update({new_p.id:new_p})
        return new_p
        
    def clear_palette(self, **kwargs):
        id = kwargs.get('id')
        palette = kwargs.get('palette')
        if not palette and id is not None:
            palette = self.AppliedPalettes.get(id)
        if palette:
            #palette.unlink()
            #palette.sequencer.stop()
            #if palette.id in self.AppliedPalettes:
            #    del self.AppliedPalettes[palette.id]
            sq = palette.sequencer
            self.AppliedPalettes.del_child(palette)
            if sq.stopping:
                sq.stopped.wait()
                sq.remove_controllers()
            if not len(sq.controller_set):
                self.del_sequencer(sequencer=sq)
            
            #print 'palette deleted'
            
    def request_osc_updates(self):
        self._osc_dumps_pending.clear()
        objnames = ['CategoryPalettes']
        self.osc_node.send_message(address='request-dump', value=objnames, to_master=True)
        
    def on_osc_dump_response(self, **kwargs):
        values = kwargs.get('values')
        if values[0] == 'dump-complete':
            keys = self._osc_dumps_pending.copy().keys()
            keys.sort()
            for key in keys:
                self.from_json(string=self._osc_dumps_pending[key])
                del self._osc_dumps_pending[key]
            #print 'ds dump-complete. keys=', self._osc_dumps_pending.keys()
            return
        #print 'ds adding dump ', values[0]
        self._osc_dumps_pending[values[0]] = values[1]
        
    def on_osc_request_dump(self, **kwargs):
        if self.osc_node.oscMaster:
            values = kwargs.get('values')
            client = kwargs.get('client')
            #print 'main...client = ', client
            if len(values):
                for i, key in enumerate(values):
                    s = self.to_json(saved_child_objects=[key], indent=None)
                    self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
            else:
                s = self.to_json(indent=None)
                self.osc_node.send_message(address='dump-response', value=[0, s], client=client)
            self.osc_node.send_message(address='dump-response', value='dump-complete', client=client)
        
    def on_osc_child_added(self, **kwargs):
        pass
        
    def clear_show(self):
        for key in self.AppliedPalettes.keys()[:]:
            self.clear_palette(id=key)
        for palettes in self.CategoryPalettes.itervalues():
            for palette in palettes.itervalues():
                palette.stored_values.clear()
                palette.name = str(palette.Index)
        
        
    def load_globals(self, **kwargs):
        pass
    def save_globals(self, **kwargs):
        return {}
        
    def get_file_data(self, **kwargs):
        d = {}
        for key in ['CategoryPalettes']:
            filename = '.'.join([key, 'js'])
            d[filename] = self._get_saved_attr(saved_child_objects=[key])
        return d
        
    def shutdown(self):
        self.clock.stop(blocking=True)
    @property
    def fps(self):
        return self.clock.fps
        
    def on_sequencer_state_changed(self, **kwargs):
        obj = kwargs.get('obj')
        state = kwargs.get('state')
        if not state:
            if obj.id in self.Sequencers and not obj.persistent and len(obj.controllers) == 0:
                self.del_sequencer(sequencer=obj)
            
#    def _deserialize_child(self, d):
#        clsname = d['saved_class_name']
#        if clsname == 'EffectPalette':
#            if d['attrs']['id'] in self.EffectPalettes:
#                return False
#            pkwargs = d.copy()
#            pkwargs.update({'root_category':self.root_category})
#            p = EffectPalette(**pkwargs)
#            return p
    def _deserialize_child(self, d):
        clsname = d['saved_class_name']
        #objid = d['attrs']['id']
        if clsname == 'CategoryPaletteGroup':
            gid = d['attrs']['id']
            self.CategoryPalettes[gid]._load_saved_attr(d)
        
